// libpcsclite1:i386                      1.8.4-1+deb7u1

#ifdef WIN32
#undef UNICODE
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h> // strcmp

#ifdef __APPLE__
#include <PCSC/winscard.h>
#include <PCSC/wintypes.h>
#else
#include <winscard.h>
#endif

#ifdef WIN32
static char *pcsc_stringify_error(LONG rv)
{
    static char out[20];
    sprintf_s(out, sizeof(out), "0x%08X", rv);
    return out;
}
#endif

/// colors
#define RED     "\x1b[31m"
#define GREEN   "\x1b[32m"
#define YELLOW  "\x1b[33m"
#define CYAN    "\x1b[36m"
#define RESET   "\x1b[0m"

/// notify r/w errors at the selected sc reader or smart card
/// f   : called function
/// rv  : error message
#define CHECK(f, rv) \
    if (SCARD_S_SUCCESS != rv) \
{ \
    printf(f RED" : %s\n"RESET, pcsc_stringify_error(rv)); \
    return -1; \
    }

void    hexPrintFromByteArray(size_t p, BYTE array[], size_t sz)
{
    size_t i;
    for(i = 0; i < sz; i++)
    {
        if ((i == p) && (p != 0)) printf("\n");
        printf(YELLOW"%02X "RESET, array[i]);
    }
    printf("\n");
}

void    hexPrintFromCharArray(char charArray[], size_t len)
{
    size_t i;
    for (i = 0; i < len; i++)
        printf("%02X ", charArray[i]);
    printf("\n");
}

void    onlyCharPrint(BYTE fC, BYTE array[])
{
    size_t i;
    for(i=0; array[i] != fC; i++)
        printf(CYAN"%c  "RESET, array[i]);        /// char message
    printf("\n");
}

int main(int argc, char *argv[])
{
    int argToRead   = 3;
    int argToWrite  = 4;
    unsigned int i;

    BYTE    fC = 0x7C;                      /// filled character '|'
    BYTE    sz = 0x50;                      /// size of register into morpho card (data to write)
//    int SZ = sz;
//    printf("SZ = %i\n", SZ);

    printf("# arguments : %i\n", argc - 1);
    printf("this program was called with: " "%s\n", argv[0]);
    if (1 < argc)
        for (i = 1; i < (unsigned)argc; i++)
            printf("argv[%i] : %s | size : %u\n", i, argv[i], strlen(argv[i]));

    // reader number    argv[1]: 1, 2...
    // first argument   argv[2]: r/w
    // second argument  argv[3]: Data to write

    if ((argc == argToRead) && (strcmp(argv[2],"r") == 0))
    {
        printf("read mode\n");
    }
    else if ((argc == argToWrite) && (strcmp(argv[2],"w") == 0))
    {
        printf("write mode\n");
        for (i = 0; i < strlen(argv[3]); i++)
            printf("%c  ", argv[3][i]);
        printf("\n");
        hexPrintFromCharArray(argv[3], strlen(argv[3]));
        ///
        /// size to set at apdu's arrays
        sz = (BYTE)(strlen(argv[3])+1); /// 1 filled character at end
        printf("sz = %i\n", sz);
        ///
    }
    else
    {
        printf("no valid entry\n");
        return -1;
    }

    // right number of arguments

    int nScReader = 0;
    if (strlen(argv[1]) == 1)               /// if argument has one character
        nScReader   = argv[1][0]-0x30;      /// convert char to int


    LONG                rv;

    SCARDCONTEXT        hContext;
    LPTSTR              mszReaders;
    SCARDHANDLE         hCard;
    DWORD               dwReaders,
            dwActiveProtocol,
            dwRecvLength;       /// Length of the response.

    SCARD_IO_REQUEST    pioSendPci;

    BYTE                pbRecvBuffer[258];  /// Response from the card.

    // write apdu array
    BYTE                reg = 0x03;
    BYTE writeApdu[]    = { 0x00, 0xD2, 0x01, reg, sz,
                            fC, fC, fC, fC, fC, fC, fC, fC, fC, fC,
                            fC, fC, fC, fC, fC, fC, fC, fC, fC, fC,
                            fC, fC, fC, fC, fC, fC, fC, fC, fC, fC,
                            fC, fC, fC, fC, fC, fC, fC, fC, fC, fC,
                            fC, fC, fC, fC, fC, fC, fC, fC, fC, fC,
                            fC, fC, fC, fC, fC, fC, fC, fC, fC, fC,
                            fC, fC, fC, fC, fC, fC, fC, fC, fC, fC,
                            fC, fC, fC, fC, fC, fC, fC, fC, fC, fC
                          };  // char array data with write apdu only

    size_t sz_data = sizeof(writeApdu);
    if (argc == argToWrite)     /// write mode
    {
        hexPrintFromByteArray(5, writeApdu, sz_data);
        for (i = 0; i < strlen(argv[3]); i++)
        {
            writeApdu[i+5] = (BYTE)argv[3][i]; /// five positions by instruction
        }
        hexPrintFromByteArray(5, writeApdu, sz_data);
        /// Here, the write apdu array is ready
    }

    /// select app from Morpho card
    BYTE selAppApdu[]   = { 0x00, 0xA4, 0x04, 0x00, 0x07, fC, fC, fC, fC, fC, fC, fC, fC };
    /// application id to select
    BYTE appId[]        = { 0xA0, 0x00, 0x00, 0x00, 0x05, 0x01, 0x01, 0x00 };
    for (i = 0; i < sizeof(appId); i++)
        selAppApdu[i+5] = appId[i];

    //    hexPrint(0, selAppApdu, sizeof(selAppApdu));

    /// read apdu
    BYTE readApdu[] = { 0x00, 0xB2, 0x01, reg, 0x00, sz };

    /// get response apdu
    BYTE getResponseApdu[] = { 0x00, 0xC0, 0x00, 0x00, sz };

    /// \brief  This function creates a communication context to the PC/SC Resource Manager.
    ///         This must be the first function called in a PC/SC application.
    /// \param  hContext: Returned reference to this connection
    /// \return SCARD_S_SUCCESS : Successful
    ///         SCARD_E_INVALID_VALUE : Invalid scope type passed

    rv = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &hContext);
    printf("rv : %ld\n", rv);
    CHECK("SCardEstablishContext", rv);

    /// Is SCARD_AUTOALLOCATE defined?
#ifdef SCARD_AUTOALLOCATE
    /*!
     * If value pointed by dwReaders is specified as SCARD_AUTOALLOCATE, then mszReaders is casted as a pointer to a pointer,
     * and receives the address of a block of memory containing the character string, allocated by the PC/SC API.
     * This block of memory must be deallocated by the calling application with SCardFreeMemory.
     * On success, mszReaders is a multi-string and separated by a null character ('\0') and ended by a double null character
     * (e.g. "Reader A\0Reader B\0\0").
     */
    dwReaders = SCARD_AUTOALLOCATE;
    /// \brief  This function returns a list of currently available readers on the system. mszReaders is a pointer to a
    ///         character string which will be allocated by the application.
    /// \param  mszReaders  : Multi-string with list of readers.
    /// \param  dwReaders   : Size of multi-string buffer including NULL’s.
    /// \return SCARD_S_SUCCESS             : Successful
    ///         SCARD_E_INVALID_HANDLE      : Invalid Scope Handle
    ///         SCARD_E_INSUFFICIENT_BUFFER : Reader buffer not large enough
    ///         SCARD_E_READER_UNAVAILABLE  : No readers available.
    rv = SCardListReaders(hContext, NULL, (LPTSTR)&mszReaders, &dwReaders);
    CHECK("SCardListReaders", rv);
#else
    rv = SCardListReaders(hContext, NULL, NULL, &dwReaders);
    CHECK("SCardListReaders", rv);

    mszReaders = calloc(dwReaders, sizeof(char));
    rv = SCardListReaders(hContext, NULL, mszReaders, &dwReaders);
    CHECK("SCardListReaders", rv);
#endif /*!SCARD_AUTOALLOCATE*/

    /// Here It's present one reader at least
    int n = 0;              /// number of present readers
    char *Current;          /// Pointer to the current reader detected
    char reader[6][100];    /// max six usb ports, one port by each sc reader (names)
    for (Current = mszReaders; *Current; Current += strlen(Current) + 1)
    {
        strcpy(reader[n], Current);
        printf(GREEN "reader name %i : %s\n" RESET, ++n, Current);
    }
    printf("number of present readers = %i\n", n);

    /// targetReader is the name of present reader to connect
    char *targetReader = "";
    (0 < nScReader && nScReader <= n) ? (targetReader = reader[nScReader-1]) : (targetReader = reader[0]);
    printf(GREEN "target reader : %s\n" RESET, targetReader);

    /// \brief  This function establishes a connection to the friendly name of the reader specified in mszReaders. (Reader A\0)
    ///         The first connection will power up and perform a reset on the card.
    /// \return SCARD_S_SUCCESS - Successful
    ///         SCARD_E_NO_SMARTCARD - Smartcard is not inserted.
    ///         SCARD_E_NOT_READY - Could not allocate the desired port.
    ///         SCARD_E_INVALID_VALUE - Invalid sharing mode, requested protocol, or reader name.
    ///         SCARD_E_READER_UNAVAILABLE - Could not power up the reader or card.
    ///         SCARD_E_UNSUPPORTED_FEATURE - Protocol not supported.
    ///         SCARD_E_SHARING_VIOLATION - Someone else has exclusive rights.
    ///         SCARD_E_INVALID_HANDLE - Invalid hContext handle.
    rv = SCardConnect(hContext,                                 /// IN, Connection context to the PC/SC Resource Manager.
                      targetReader,                             /// IN, Reader name to connect to, first one by default.
                      SCARD_SHARE_SHARED,                       /// IN, Mode of connection type: exclusive or shared.
                      SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1,    /// IN, Desired protocol use.
                      &hCard,                                   /// OUT, Handle to this connection.
                      &dwActiveProtocol                         /// OUT, Established protocol to this connection.
                      );
    /*!
     * Value of dwShareMode Meaning
     * SCARD_SHARE_SHARED		This application will allow others to share the reader.
     * SCARD_SHARE_EXCLUSIVE	This application will NOT allow others to share the reader.
     * Value of dwPreferredProtocols Meaning
     * SCARD_PROTOCOL_T0		Use the T=0 protocol.
     * SCARD_PROTOCOL_T1		Use the T=1 protocol
     * SCARD_PROTOCOL_RAW		Use with memory type cards.
     */
    CHECK("SCardConnect", rv);

    switch(dwActiveProtocol)
    {
    case SCARD_PROTOCOL_T0:                 /// response
        pioSendPci = *SCARD_PCI_T0;         /// definition
        break;

    case SCARD_PROTOCOL_T1:
        pioSendPci = *SCARD_PCI_T1;
        break;
    }

    /// send selAppApdu command
    /// Parameters to SCardTransmit function
    dwRecvLength = sizeof(pbRecvBuffer);
    rv = SCardTransmit(hCard,               /// IN      Connection made from SCardConnect.
                       &pioSendPci,         /// INOUT   Structure of protocol information.
                       selAppApdu,          /// IN      APDU to send to the card.
                       sizeof(selAppApdu),  /// IN      Length of the APDU.
                       NULL,                /// INOUT   Structure of protocol information. (NULL) possible.
                       pbRecvBuffer,        /// OUT     Response from the card.
                       &dwRecvLength        /// INOUT   Length of the response.
                       );
    CHECK("SCardTransmit", rv);

    printf("response 1 : ");
    hexPrintFromByteArray(0, pbRecvBuffer, dwRecvLength);

    if (argc == argToWrite)
    {
        /// send write apdu command
        dwRecvLength = sizeof(pbRecvBuffer);
        rv = SCardTransmit(hCard, &pioSendPci, writeApdu, /*sizeof(writeApdu)*/(5+sz), NULL, pbRecvBuffer, &dwRecvLength);
        CHECK("SCardTransmit", rv);

        printf("response 2 : ");
        hexPrintFromByteArray(0, pbRecvBuffer, dwRecvLength);
    }

    /// send read apdu command
    dwRecvLength = sizeof(pbRecvBuffer);
    rv = SCardTransmit(hCard, &pioSendPci, readApdu, sizeof(readApdu), NULL, pbRecvBuffer, &dwRecvLength);
    CHECK("SCardTransmit", rv);

    printf("response 3 : ");
    hexPrintFromByteArray(0, pbRecvBuffer, dwRecvLength);

    /// send get response apdu command
    dwRecvLength = sizeof(pbRecvBuffer);
    rv = SCardTransmit(hCard, &pioSendPci, getResponseApdu, sizeof(getResponseApdu), NULL, pbRecvBuffer, &dwRecvLength);
    CHECK("SCardTransmit", rv);

    printf("response 4 : \n");
    hexPrintFromByteArray(dwRecvLength-2, pbRecvBuffer, dwRecvLength);
    onlyCharPrint(fC, pbRecvBuffer);    // stop at filled character first

    /// disconnect
    rv = SCardDisconnect(hCard, SCARD_LEAVE_CARD);
    CHECK("SCardDisconnect", rv);

#ifdef SCARD_AUTOALLOCATE
    /// free memory
    rv = SCardFreeMemory(hContext, mszReaders);
    CHECK("SCardFreeMemory", rv);
#else
    free(mszReaders);
#endif /*!SCARD_AUTOALLOCATE*/

    rv = SCardReleaseContext(hContext);
    CHECK("SCardReleaseContext", rv);

    return 0;
}
